

#include "dataStream.h"


dataStream::dataStream() { //Creates empty dataStream
	this->dataBuffer = NULL; //Set data to null
	this->length = 0; //0 length
	this->gPos = 0; //Get position is 0
	this->pPos = 0; //Put position is 0
}

dataStream::dataStream(char* buf, unsigned int len) { //Creates dataStream based on existing data 
	this->dataBuffer = buf; //Allocate the space
	this->length = len; //Set the length
	this->gPos = 0; //Get position is 0
	this->pPos = 0; //Put position is 0
}

dataStream::dataStream(unsigned int len) { //Creates a dataStream and allocates len bytes initialy
	this->dataBuffer = new char[len]; //Allocate the space specified
	this->length = len; //Set the length to the length we just allocated
	this->gPos = 0; //Get position is 0
	this->pPos = 0; //Put position is 0;
}

void dataStream::save(std::string fileName) { //Save to a file on disk
	std::fstream fileStream(fileName,std::ios::trunc | std::ios::binary | std::ios::out);
	if (fileStream.is_open()) {
		fileStream.write(this->getBuffer(),this->getLength());
		fileStream.close();
	}
}


bool dataStream::uncompressData(unsigned int chunks) {

	unsigned int curAllocate = chunks;
	Bytef* outBuffer = new Bytef[curAllocate];
	unsigned int outBufferSize = curAllocate;
	int result = uncompress(outBuffer,reinterpret_cast<uLongf*>(&outBufferSize),reinterpret_cast<Bytef*>(this->getBuffer()),this->getLength());
	while (result==Z_BUF_ERROR)
	{//Keep trying until the output buffer is big enough.
		delete [] outBuffer; //Delete the old buffer
		curAllocate+=chunks; //Increase buffer size
		outBuffer = new Bytef[curAllocate]; //Allocate the new buffer size
		outBufferSize = curAllocate; //Set the size of the buffer
		result = uncompress(outBuffer,reinterpret_cast<uLongf*>(&outBufferSize),reinterpret_cast<Bytef*>(this->getBuffer()),this->getLength()); //Try decompressing again
	}
	if (result!=Z_OK)
	{
		printf("Error in uncompress() in function dataStream::uncompressData: %i\n",result);
		return false;
	}
		delete [] this->dataBuffer; //Delete the old compressed data
		this->dataBuffer = reinterpret_cast<char*>(outBuffer); //Set the data for this dataStream to the new uncompressed data
		this->length = outBufferSize;
		this->seekg(0);
		return true;
}

bool dataStream::compressData() { //Compress all the data. Old data will be replaced with the expanded data
		uLongf destSize = (this->getLength() * 1.001) + 12;
		Bytef * destBuffer = new Bytef[destSize];
		int result = compress(destBuffer,&destSize,reinterpret_cast<Bytef*>(this->getBuffer()),this->getLength());
		if (result==Z_OK)
		{
			delete [] this->dataBuffer; //Delete the old uncompressed data
			this->dataBuffer = reinterpret_cast<char*>(destBuffer); //Set the new data
			this->length = destSize; //Set the length
			this->seekg(0); //Seek to 0
			this->seekp(0); //Seek to 0
			return true;
		}else{
			return false;
		}
}

void dataStream::load(std::string fileName) { //Load a file from disk to the datastream
	std::fstream fileStream(fileName,std::ios::binary | std::ios::in);
	if (fileStream.is_open()) { //If the handle opened sucessfully
		fileStream.seekg(0,std::ios::end); //Seek to the end
		unsigned int fileLength = fileStream.tellg(); //Get the length of the file
		fileStream.seekg(0,std::ios::beg); //Seek back to the beginning
		char* fileBuffer = new char[fileLength];
		fileStream.read(fileBuffer,fileLength); //Read the file contents
		this->seekp(0); //Seek to the beginning of the dataStream
		this->writeBytes(fileBuffer,fileLength); //Copy the file data to the dataStream
		delete [] fileBuffer; //Delete the buffer
		fileStream.close();
	}
}

unsigned int dataStream::getLength() { //Get size of the buffer
	return this->length; //Return length of data
}
unsigned int dataStream::tellp() { //returns put position
	return this->pPos; //Return current put position
}

unsigned int dataStream::tellg() { //returns get position
	return this->gPos; //Return current get position
}

char* dataStream::getBuffer() { //Returns a pointer to the buffer
	return this->dataBuffer; //Return pointer to the data buffer
}

bool dataStream::seekp(unsigned int pos, direction dir) { //Seek put position
	switch (dir) {
	case beg: { //Seek relative to the beginning (0)
				if (offsetInRange(pos)) {
					this->pPos = pos;
					return true;
				}
				break;
			  }
	case cur: { //Seek relative to current position
				if (offsetInRange(this->pPos+pos)) {
					this->pPos += pos;
					return true;
				}
				break;
			  }
	case end: { //Seek relative to end of the data
				if (offsetInRange(this->getLength()-pos)) {
					this->pPos = this->getLength()-pos;
					return true;
				}
				break;
			  }
	}
	return false;
}

bool dataStream::seekg(unsigned int pos, direction dir) { //Seek get position
	switch (dir) {
	case beg: {  //Seek relative to the beginning (0)
				if (offsetInRange(pos)) {
					this->gPos = pos;
					return true;
				}
				break;
			  }
	case cur: { //Seek relative to current position
				if (offsetInRange(this->gPos+pos)) {
					this->gPos += pos;
					return true;
				}
				break;
			  }
	case end: { //Seek relative to end of the data
				if (offsetInRange(this->getLength()-pos)) {
					this->gPos = this->getLength()-pos;
					return true;
				}
				break;
			  }
	}
	return false;
}

bool dataStream::checkWriteOverflow(unsigned int nBytes) { //Checks if there is enough room to write nBytes to the buffer
	if (offsetInRange(this->tellp()+nBytes)) {
		return false; //Return false; an overflow has not occured, as the offset is in range
	}else{
		return true; //True; overflow occured
	}
}
bool dataStream::offsetInRange(unsigned int offset) { //Checks if the specified offset is in range of the buffer
	if (offset <= this->getLength() && offset >= 0) { //If offset is smaller or equal then the length, and greater or equal to 0, then it is in bounds.
		return true; //Offset is in bounds
	}else{ 
		return false; //Offset is out of bounds
	}
}
bool dataStream::checkReadOverflow(unsigned int nBytes) { //Checks if nBytes can be read from the stream without going past the end of the buffer
	if (offsetInRange(this->tellg()+nBytes)) {
		return false; //Return false; an overflow has not occured, as the offset is in range
	}else{
		return true; //True; overflow occured
	}
}

void dataStream::expand(unsigned int bytes) { //Expand the buffer by bytes number of bytes
	this->length += bytes;
	this->dataBuffer = (char*)realloc((void*)dataBuffer,this->length);
}


unsigned char dataStream::readByte() {
	if (checkReadOverflow(sizeof(char))) {
		return 0x00;
	}
	unsigned char retValue = *(unsigned char*)(dataBuffer + gPos);
	gPos += sizeof(char);
	return retValue;
}
unsigned short dataStream::readWord() {
	if (checkReadOverflow(sizeof(short))) {
		return 0x00;
	}
	unsigned short retValue = *(unsigned short*)(dataBuffer + gPos);
	gPos += sizeof(short);
	return retValue;
}
unsigned int dataStream::readDword() {
	if (checkReadOverflow(sizeof(int))) {
		return 0x00;
	}
	unsigned int retValue = *(unsigned int*)(dataBuffer + gPos);
	gPos += sizeof(int);
	return retValue;
}
void dataStream::readBytes(char* dest, unsigned int len) {
	if (checkReadOverflow(len)) {
		return;
	}
	memcpy(dest,dataBuffer + gPos,len);
	gPos += len;
}
dataStream* dataStream::readBytes(unsigned int len) { //Reads bytes, but makes a new dataStream and readsa the bytes into it

	char* buffer = new char[len];
	readBytes(buffer,len); //Read the bytes.
	dataStream *retVal = new dataStream(buffer,len); //Allocate dataStream
	return retVal;
}

float dataStream::readFloat() {
	if (checkReadOverflow(sizeof(float))) {
		return 0x00;
	}
	float retValue = *(float*)(dataBuffer + gPos);
	gPos += sizeof(float);
	return retValue;
}
bool dataStream::readBool() {
	if (checkReadOverflow(sizeof(bool))) {
		return 0x00;
	}
	bool retValue = *(bool*)(dataBuffer + gPos);
	gPos += sizeof(bool);
	return retValue;
}

bool dataStream::readBool4() { //Read 4 byte bool
	return readDword()==1 ? (true) : false;
}

std::string dataStream::readString() {
	int Size = read<int>();
	if (Size>0)
	{
	char *Buff = new char[Size+1];
	readBytes(Buff,Size);
	Buff[Size] = '\0';
	std::string ret;
	ret += Buff;
	delete [] Buff;
	return ret;
	}else{
		return "";
	}
}

// Writing
void dataStream::writeByte(unsigned char value) {
	if (checkWriteOverflow(sizeof(char))) { //Not enough room
		expand(sizeof(char)); //Expand the buffer
	}
	*(unsigned char*)(dataBuffer + pPos) = value;
	pPos += sizeof(char);
}
void dataStream::writeBool4(bool value) {
	value==true ? write<int>(1) : write<int>(0);
}

void dataStream::writeWord(unsigned short value) {
	if (checkWriteOverflow(sizeof(unsigned short))) { //Not enough room
		expand(sizeof(unsigned short)); //Expand the buffer
	}
	*(unsigned short*)(dataBuffer + pPos) = value;
	pPos += sizeof(unsigned short);
}
void dataStream::writeDword(unsigned int value) {
	if (checkWriteOverflow(sizeof(unsigned int))) { //Not enough room
		expand(sizeof(unsigned int)); //Expand the buffer
	}
	*(unsigned int*)(dataBuffer + pPos) = value;
	pPos += sizeof(unsigned int);
}
void dataStream::writeFloat(float value) {
	if (checkWriteOverflow(sizeof(float))) { //Not enough room
		expand(sizeof(float)); //Expand the buffer
	}
	*(float*)(dataBuffer + pPos) = value;
	pPos += sizeof(float);
}
void dataStream::writeBytes(char* value, unsigned len) {
	if (checkWriteOverflow(len)) { //Not enough room
		expand(len); //Expand the buffer
	}
	memcpy(this->dataBuffer + this->pPos,value, len); //Copy the data over 
	this->pPos += len;
}

void dataStream::writeString(std::string value) {
	write<int>(value.length()); //Write the string length
	writeBytes(const_cast<char*>(value.c_str()),value.length()); //Write the string
}